﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Collections.Specialized;
using System.Windows.Input;
using Kokomo.Environment.Documents;

namespace Kokomo.PeInspector.Reflection
{
	class AssemblyListSearchViewModel : SearchViewModel<SearchResult>
	{
		public AssemblyListSearchViewModel(
			IList<Assembly> assemblyList,
			Func<Assembly, AssemblyIndex> assemblyIndexDelegate,
			ICommand navigateToCommand
			)
			: base()
		{
			if (assemblyList == null) throw new ArgumentNullException("assemblyList");
			if (assemblyIndexDelegate == null) throw new ArgumentNullException("assemblyIndexDelegate");
			if (navigateToCommand == null) throw new ArgumentNullException("navigateToCommand");

			this.AssemblyList = assemblyList;
			INotifyCollectionChanged notify = assemblyList as INotifyCollectionChanged;
			if (notify != null)
			{
				notify.CollectionChanged += new NotifyCollectionChangedEventHandler(AssemblyList_CollectionChanged);
			}

			this._assemblyIndexDelegate = assemblyIndexDelegate;
			this.NavigateToCommand = navigateToCommand;

			// Set options
			this.IncludeNonPublicTypes = true;
			this.IncludeNonPublicMembers = true;
			this.IncludeTypes = true;
			this.IncludeFields = true;
			this.IncludeMethods = true;
			this.IncludeProperties = true;
			this.IncludeEvents = true;
		}

		public ICommand NavigateToCommand { get; private set; }
		private Func<Assembly, AssemblyIndex> _assemblyIndexDelegate;

		void AssemblyList_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			this.InvalidateSearch();
		}

		private int _maxResultCount;
		public int MaxResultCount
		{
			get { return this._maxResultCount; }
			set
			{
				this._maxResultCount = value;
				this.OnPropertyChanged("MaxResultCount");
			}
		}

		public IList<Assembly> AssemblyList { get; private set; }

		protected override IEnumerable<SearchResult> OnSearchAsync(string searchTerm, int searchVersion)
		{
			if (string.IsNullOrEmpty(searchTerm)) return null;

			// Get array to avoid synchronization issues
			Assembly[] assemblies;
			lock (this.AssemblyList)
			{
				assemblies = this.AssemblyList.ToArray();
			}

			ProgressInfo progress = new ProgressInfo()
			{
				Maximum = assemblies.Length
			};
			this.SearchProgress = progress;
			IEnumerable<SearchResult> searchResults = this.GetSearchResults(
				assemblies,
				searchVersion,
				progress
				).Take(500);

			return searchResults;
		}

		private IEnumerable<SearchResult> GetSearchResults(
			Assembly[] assemblies,
			int searchVersion,
			ProgressInfo progress
			)
		{
			for (int i = 0; i < assemblies.Length; i++)
			{
				progress.Progress = i + 1;

				Assembly assembly = assemblies[i];
				AssemblyIndex index = this._assemblyIndexDelegate(assembly);

				foreach (var type in index.Types)
				{
					if (searchVersion != this.SearchVersion) yield break;

					MemberViewInfo typeViewInfo = MemberViewInfo.GetMemberViewInfo(type);
					if (this.IncludeTypes && TypeMatches(type)) yield return new SearchResult(type, typeViewInfo, new MemberViewInfo());

					if (this.IncludeNonPublicTypes || type.IsPublic || type.IsVisible || type.IsNestedPublic)
					{
						if (this.IncludeFields)
						{
							FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
							foreach (var field in fields)
							{
								if (FieldMatches(field)) yield return new SearchResult(field, MemberViewInfo.GetMemberViewInfo(field), typeViewInfo);
							}
						}
						if (this.IncludeMethods)
						{
							MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
							foreach (var method in methods)
							{
								if (MethodMatches(method)) yield return new SearchResult(method, MemberViewInfo.GetMemberViewInfo(method), typeViewInfo);
							}
						}
						if (this.IncludeProperties)
						{
							PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
							foreach (var property in properties)
							{
								if (PropertyMatches(property)) yield return new SearchResult(property, MemberViewInfo.GetMemberViewInfo(property), typeViewInfo);
							}
						}
						if (this.IncludeEvents)
						{
							EventInfo[] events = type.GetEvents(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
							foreach (var eventInfo in events)
							{
								if (EventMatches(eventInfo)) yield return new SearchResult(eventInfo, MemberViewInfo.GetMemberViewInfo(eventInfo), typeViewInfo);
							}
						}
					}
				}
			}
		}

		#region Options
		private bool _includeNonPublicMembers;
		public bool IncludeNonPublicMembers
		{
			get { return this._includeNonPublicMembers; }
			set
			{
				this._includeNonPublicMembers = value;
				this.OnPropertyChanged("IncludeNonPublicMembers");
				this.InvalidateSearch();
			}
		}

		private bool _includeSpecialNames;
		public bool IncludeSpecialNames
		{
			get { return this._includeSpecialNames; }
			set
			{
				this._includeSpecialNames = value;
				this.OnPropertyChanged("IncludeSpecialNames");
				this.InvalidateSearch();
			}
		}

		private bool _includeNonPublicTypes;
		public bool IncludeNonPublicTypes
		{
			get { return this._includeNonPublicTypes; }
			set
			{
				this._includeNonPublicTypes = value;
				this.OnPropertyChanged("IncludeNonPublicTypes");
				this.InvalidateSearch();
			}
		}

		private bool _includeTypes;
		public bool IncludeTypes
		{
			get { return this._includeTypes; }
			set
			{
				this._includeTypes = value;
				this.OnPropertyChanged("IncludeTypes");
				this.InvalidateSearch();
			}
		}

		private bool _includeFields;
		public bool IncludeFields
		{
			get { return this._includeFields; }
			set
			{
				this._includeFields = value;
				this.OnPropertyChanged("IncludeFields");
				this.InvalidateSearch();
			}
		}

		private bool _includeMethods;
		public bool IncludeMethods
		{
			get { return this._includeMethods; }
			set
			{
				this._includeMethods = value;
				this.OnPropertyChanged("IncludeMethods");
				this.InvalidateSearch();
			}
		}

		private bool _includeProperties;
		public bool IncludeProperties
		{
			get { return this._includeProperties; }
			set
			{
				this._includeProperties = value;
				this.OnPropertyChanged("IncludeProperties");
				this.InvalidateSearch();
			}
		}

		private bool _includeEvents;
		public bool IncludeEvents
		{
			get { return this._includeEvents; }
			set
			{
				this._includeEvents = value;
				this.OnPropertyChanged("IncludeEvents");
				this.InvalidateSearch();
			}
		}

		#endregion
		#region Predicates
		private bool NameMatches(string name)
		{
			StringComparison comparison = (this.IsCaseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase);

			return this.RequiresExactMatch
				? string.Equals(name, this.SearchTerm, comparison)
				: name.Contains(this.SearchTerm, comparison)
				;
		}

		private bool TypeMatches(Type type)
		{
			return
				(this.IncludeNonPublicMembers || type.IsVisible || type.IsPublic || type.IsNestedPublic)
				&& this.NameMatches(type.Name)
				;
		}
		private bool FieldMatches(FieldInfo field)
		{
			return
				(this.IncludeNonPublicMembers || field.IsPublic)
				&& (this.IncludeSpecialNames || !field.IsSpecialName)
				&& this.NameMatches(field.Name)
				;
		}
		private bool MethodMatches(MethodInfo method)
		{
			return
				(this.IncludeNonPublicMembers || method.IsPublic)
				&& (this.IncludeSpecialNames || !method.IsSpecialName)
				&& this.NameMatches(method.Name)
				;
		}
		private bool PropertyMatches(PropertyInfo property)
		{
			return
				(this.IncludeNonPublicMembers || (property.GetAccessors().Length > 0))
				&& (this.IncludeSpecialNames || !property.IsSpecialName)
				&& this.NameMatches(property.Name)
				;
		}
		private bool EventMatches(EventInfo eventInfo)
		{
			return
				(this.IncludeNonPublicMembers || eventInfo.GetAddMethod() != null)
				&& (this.IncludeSpecialNames || !eventInfo.IsSpecialName)
				&& this.NameMatches(eventInfo.Name)
				;
		}

		#endregion
	}
}
